#pragma once
#include "util.hpp"
#include "room.hpp"
#include "Online.hpp"
#include "session.hpp"
#include "match.hpp"

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;
class gobang_server
{
#define WWWROOT "./wwwroot"
private:
    // 静态资源请求， 默认是登录页面
    void file_handle(wsserver_t::connection_ptr &conn)
    {
        // 1、获取uri
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        std::string pathname = _web_root + uri;

        if (pathname.back() == '/')
        {
            pathname += "login.html";
        }
        // 2、读取文件内容
        // std::cout << "pathname: " << pathname << std::endl;
        // Json::Value resp;
        std::string body;
        if (!Fileop::ReadFile(pathname, body))
        {
            std::string ErrorPath = _web_root + "/error.html";
            Fileop::ReadFile(ErrorPath, body);
            conn->set_status(websocketpp::http::status_code::not_found);
        }
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(body);
    }
    
    void http_resp(wsserver_t::connection_ptr &conn,
                   bool result, websocketpp::http::status_code::value code, std::string reason)
    {
        Json::Value resp;

        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        json_util::serialize(resp, resp_body);
        conn->set_status(code);
        conn->set_body(resp_body);
        conn->append_header("Content-Type", "application/json");
    }
    
    // 注册请求
    void reg(wsserver_t::connection_ptr &conn)
    {
        websocketpp::http::parser::request req = conn->get_request();
        std::string body = req.get_body();

        Json::Value login_info;
        bool ret = json_util::deserialize(login_info, body);
        if (!ret)
        {
            LOG(ERROR, "注册信息反序列化失败\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误失败");
        }

        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            LOG(ERROR, "注册用户名密码不完整\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名和密码");
        }

        ret = _uc.InsertUser(login_info);
        if (!ret)
        {
            LOG(ERROR, "注册用户名重复\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名已经被占用!");
        }

        return http_resp(conn, true, websocketpp::http::status_code::ok, "注册用户成功!");
    }
    // 登录请求
    void login(wsserver_t::connection_ptr &conn)
    {
        websocketpp::http::parser::request req = conn->get_request();
        std::string body = req.get_body();
        LOG(INFO, "body: %s", body.c_str());

        Json::Value login_info;
        bool ret = json_util::deserialize(login_info, body);
        if (!ret)
        {
            LOG(ERROR, "登录信息反序列化失败\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误失败");
        }

        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            LOG(ERROR, "登录用户名密码不完整\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名和密码");
        }

        if (!_uc.Login(login_info))
        {
            LOG(ERROR, "登录用户名或密码错误\n");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名或密码错误");
        }

        // json中没有uid
        Json::Value user_info;
        std::string user_info_str;
        _uc.SelectUserByName(user_info, login_info["username"].asString());
        if (!json_util::serialize(user_info, user_info_str))
        {
            LOG(INFO, "json_util::serialize(user_info, user_info_str) fail\n");
        }

        LOG(INFO, "user_info_str: %s\n", user_info_str.c_str());
        int uid = user_info["id"].asInt();

        // std::cout << "hahahah: " << login_info["username"] << login_info["password"] << uid << std::endl;
        LOG(INFO, "uid: %d\n", uid);
        session_ptr sp = _sm.create_session(uid, LOGIN);
        if (sp.get() == nullptr)
        {
            LOG(ERROR, "登录创建会话失败\n");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
        }
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_TIMEOUT);
        // 设置cookie
        std::string cookie_ssid = "SSID=" + std::to_string(sp->GetSsid());
        conn->append_header("Set-Cookie", cookie_ssid);
        LOG(INFO, "用户登录成功!\n");
        return http_resp(conn, true, websocketpp::http::status_code::ok, "用户登录成功!");
    }

    // Cookie: SSID=1; path=/;
    bool get_cookie_val(const std::string &cookie_str, const std::string &k, std::string &v)
    {
        std::vector<std::string> single_cookies;
        StringSplit::split(cookie_str, "; ", single_cookies);

        for (auto e : single_cookies)
        {
            std::vector<std::string> line;
            StringSplit::split(e, "=", line);
            if (line.size() != 2)
                continue;
            if (line[0] == k)
            {
                v = line[1];
                return true;
            }
        }
        return false;
    }

    // 用户信息请求 Cookie: SSID=1; path=/;
    void info(wsserver_t::connection_ptr &conn)
    {
        // 1、获取cookie
        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            LOG(ERROR, "获取cookie失败, 请重新登录\n");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "获取cookie失败");
        }

        std::string ssid_s;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_s);
        if (!ret)
        {
            LOG(ERROR, "获取ssid失败, 请重新登录\n");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "获取ssid失败");
        }

        int ssid = std::stoi(ssid_s);

        session_ptr sp = _sm.get_session_by_ssid(ssid);
        if (sp.get() == nullptr)
        {
            LOG(ERROR, "获取session失败, 请重新登录\n");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "用户过期");
        }
        int uid = sp->get_user();

        std::cout << "uid : " << uid << std::endl;
        std::cout << "ssid : " << ssid << std::endl;

        Json::Value user_info;
        if (!_uc.SelectUserById(user_info, uid))
        {
            LOG(ERROR, "获取用户信息失败, 请重新登录\n");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "获取用户信息失败");
        }
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_TIMEOUT);

        std::string res_body;
        json_util::serialize(user_info, res_body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(res_body);
        conn->append_header("Content-Type", "application/json");
    }

    void http_callback(websocketpp::connection_hdl hd1)
    {
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hd1);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();

        // LOG(INFO, "httpreq: method: %s, uri: %s\n", method.c_str(), uri.c_str());

        if (strcasecmp(method.c_str(), "post") == 0 && uri == "/reg")
        {
            LOG(INFO, "reg function begin...\n");
            return reg(conn);
        }
        else if (strcasecmp(method.c_str(), "post") == 0 && uri == "/login")
        {
            LOG(INFO, "login function begin...\n");
            return login(conn);
        }
        else if (strcasecmp(method.c_str(), "get") == 0 && uri == "/info")
        {
            return info(conn);
        }
        else
        {
            file_handle(conn);
        }
    }

    // websocket长连接响应
    void ws_resp(wsserver_t::connection_ptr &conn,
                   bool result, std::string reason, std::string optype)
    {
        Json::Value resp;

        resp["optype"] = optype;
        resp["result"] = result;
        resp["reason"] = reason;
        std::string resp_body;
        json_util::serialize(resp, resp_body);
        
        conn->send(resp_body);
    }

    session_ptr get_session_by_cookie(wsserver_t::connection_ptr& conn)
    {
        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            LOG(ERROR, "获取cookie失败, 请重新登录\n");
            ws_resp(conn, false, "获取cookie失败", "hall_ready");
            return session_ptr();
        }

        std::string ssid_s;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_s);
        if (!ret)
        {
            LOG(ERROR, "获取ssid失败, 请重新登录\n");
            ws_resp(conn, false, "获取ssid失败", "hall_ready");
            return session_ptr();
        }

        int ssid = std::stoi(ssid_s);
        session_ptr sp = _sm.get_session_by_ssid(ssid);
        if (sp.get() == nullptr)
        {
            LOG(ERROR, "获取session失败, 请重新登录\n");
            ws_resp(conn, false, "用户过期", "hall_ready");
            return session_ptr();
        }
        return sp;
    }

    void wsopen_game_hall(wsserver_t::connection_ptr& conn)
    {
        // 游戏大厅
        // 1、判断客户端是否已经成功登录
        // 获取cookie
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
        {
            return;
        }
        
        // 2、是否重复登录
        if(_oum.is_in_game_hall(sp->get_user()) || _oum.is_in_game_room(sp->get_user()))
        {
            LOG(ERROR, "该用户已在线, 请重新登录\n");
            return ws_resp(conn, false, "重复登录", "hall_ready");
        }
        // 3、将当前客户端和连接加入游戏大厅
        _oum.enter_game_hall(sp->get_user(), conn);
        // 4、给客户端响应
        ws_resp(conn, true, "游戏大厅建立成功", "hall_ready");
        // 5、设置session永久存在
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_FOREVER);
    }
    void wsclose_game_hall(wsserver_t::connection_ptr& conn)
    {
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
        {
            return;
        }

        // 1、将玩家移除
        _oum.exit_game_hall(sp->get_user());

        // 2、session 设定定时销毁
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_TIMEOUT);
    }
    void open_callback(websocketpp::connection_hdl hd1)
    {
        // websocket 长连接建立之后的处理函数
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hd1);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        if(uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            return wsopen_game_hall(conn);
        }
        else if(uri == "/room")
        {
            // 建立了游戏房间的长连接
            return wsopen_game_room(conn);
        }

    }
    void wsopen_game_room(wsserver_t::connection_ptr& conn)
    {
        // 1、获取session
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
        {
            return;
        }
        LOG(INFO, "成功获取session\n");
        // 2、是否已经在游戏房间或游戏大厅中
        if(_oum.is_in_game_hall(sp->get_user()) || _oum.is_in_game_room(sp->get_user()))
        {
            LOG(ERROR, "该用户已在线, 请重新登录\n");
            return ws_resp(conn, false, "重复登录", "room_ready");
        }
        LOG(INFO, "成功进入游戏房间\n");

        // 3、判断当前用户是否已经创建好了房间
        LOG(INFO, "uid: %d\n", sp->get_user());
        room_ptr rp = _rm.get_room_by_userid(sp->get_user());
        if(rp.get() == nullptr)
        {
            LOG(ERROR, "没有找到玩家的房间信息\n");
            return ws_resp(conn, false, "没有找到玩家的房间信息", "room_ready");
        }
        LOG(INFO, "成功找到房间信息\n");

        // 4、进入房间
        _oum.enter_game_room(sp->get_user(), conn);
        LOG(INFO, "成功进入房间\n");
        // 5、设置session
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_FOREVER);
        LOG(INFO, "成功设置session过期时间\n");
        Json::Value resp;
        resp["optype"] = "room_ready";
        resp["result"] = true;
        resp["reason"] = "房间准备完毕";
        resp["room_id"] = rp->GetRoomid();
        resp["uid"] = sp->get_user();
        resp["white_id"] = rp->GetWhiteId();
        resp["black_id"] = rp->GetBlackId();
        resp["uid"] = sp->get_user();
        std::string resp_body;
        json_util::serialize(resp, resp_body);
        conn->send(resp_body);
        LOG(INFO, "发送成功\n");

    }
    void wsclose_game_room(wsserver_t::connection_ptr& conn)
    {
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
        {
            return;
        }
        // 1、在线用户管理中删除
        _oum.exit_game_room(sp->get_user());
        LOG(INFO, "用户已删除\n");
        // 2、将session销毁
        _sm.set_session_expire_time(sp->GetSsid(), SESSION_TIMEOUT);
        LOG(INFO, "session 已销毁\n");
        // 3、将玩家从游戏房间中移除
        _rm.remove_room_user(sp->get_user());
        LOG(INFO, "玩家已从游戏房间中移除\n");
    }
    void close_callback(websocketpp::connection_hdl hd1)
    {
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hd1);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        if(uri == "/hall")
        {
            // 游戏大厅的长连接断开
            return wsclose_game_hall(conn);
        }
        else if(uri == "/room")
        {
            // 游戏房间的长连接断开
            return wsclose_game_room(conn);
        }
    }

    void wsmsg_game_hall(wsserver_t::connection_ptr& conn, wsserver_t::message_ptr msg)
    {
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;
        
        // 获取请求信息，并根据请求信息进行响应，最后对客户端响应
        // 信息: 开始匹配、停止匹配
        std::string req_msg = msg->get_payload();
        Json::Value req_json;
        if(!json_util::deserialize(req_json, req_msg))
        {
            return ws_resp(conn, false, "请求信息解析失败", "hall_ready");
        }

        if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
        {
            _mm.add(sp->get_user());
            return ws_resp(conn, true, "添加匹配队列成功", "match_start");
        }
        else if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
        {
            _mm.del(sp->get_user());
            return ws_resp(conn, true, "移除匹配队列成功", "match_stop");
        }
        return ws_resp(conn, false, "未知错误", "unknow");
    }
    void wsmsg_game_room(wsserver_t::connection_ptr& conn, wsserver_t::message_ptr msg)
    {
        session_ptr sp = get_session_by_cookie(conn);
        if(sp.get() == nullptr)
            return;

        room_ptr rp = _rm.get_room_by_userid(sp->get_user());
        if(rp.get() == nullptr)
        {
            LOG(ERROR, "没有找到玩家的房间信息\n");
            return ws_resp(conn, false, "没有找到玩家的房间信息", "room_ready");
        }
        // 获取信息
        std::string req_msg = msg->get_payload();
        Json::Value req_json;
        if(!json_util::deserialize(req_json, req_msg))
        {
            return ws_resp(conn, false, "请求信息解析失败", "room_ready");
        }

        // 通过房间模块进行处理
        return rp->handle_req(req_json);
    }
    void message_callback(websocketpp::connection_hdl hd1, wsserver_t::message_ptr msg)
    {
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hd1);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();

        if(uri == "/hall")
        {
            // 游戏大厅的消息处理
            return wsmsg_game_hall(conn, msg);
        }
        else if(uri == "/room")
        {
            // 游戏房间的消息处理
            return wsmsg_game_room(conn, msg);
        }
    }

public:
    gobang_server(const std::string host, const std::string user, const std::string passwd, const std::string db,
                  uint16_t port = 3306, const std::string wwwroot = WWWROOT)
        : _web_root(wwwroot), _uc(host, user, passwd, db, port),
          _oum(), _rm(&_uc, &_oum), _sm(&_server), _mm(&_rm, &_uc, &_oum)
    {
        _server.set_access_channels(websocketpp::log::alevel::none);
        _server.init_asio();
        _server.set_reuse_addr(true);
        _server.set_open_handler(std::bind(&gobang_server::open_callback, this, std::placeholders::_1));
        _server.set_close_handler(std::bind(&gobang_server::close_callback, this, std::placeholders::_1));
        _server.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _server.set_message_handler(std::bind(&gobang_server::message_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

    ~gobang_server()
    {
    }

    void start(uint16_t port)
    {
        _server.listen(port);
        _server.start_accept();
        _server.run();
    }

private:
    std::string _web_root; // web 根目录

    UserCtl _uc;
    Online_User_Manage _oum;
    RoomManage _rm;
    SessionManage _sm;
    Matcher _mm;
    wsserver_t _server;
};
